#include "active/active_config.hpp"
#include "active/active_initializer.hpp"
#include <cstdio>
#include <zephyr/fff.h>
#include <zephyr/ztest.h>
DEFINE_FFF_GLOBALS;
FAKE_VOID_FUNC(call, int);
FAKE_VOID_FUNC(no_guard_call, int);
namespace {

ACTIVE_EVENT_TYPE_DEF(e1, int);
ACTIVE_EVENT_TYPE_DEF(e2, int, int);
ACTIVE_EVENT_TYPE_DEF(e3);
ACTIVE_EVENT_TYPE_DEF(e4, int);
ACTIVE_EVENT_TYPE_DEF(e5, int);
ACTIVE_EVENT_TYPE_DEF(e6, int, int);
ACTIVE_EVENT_TYPE_DEF(e7, int, int);
ACTIVE_EVENT_TYPE_DEF(e10, int);
int i = 0;
struct s1 {
  auto operator()() {
    auto f1 = [](auto e) {
      const auto [data] = e.Data();
      i = data;
    };
    auto f2 = [](auto e) {
      const auto [d1, d2] = e();
      i = d1 + d2;
    };
    using namespace atv::sml;
    using namespace atv;
    // clang-format off
    return make_transition_table(
        *"s1"_s + event<e1> / f1 = "s2"_s, 
         "s2"_s + event<e2> / f2 = "s1"_s,
         "s1"_s + event<e7> / f2 = "s3"_s, 
         "s3"_s + event<e6> / f2 = "s1"_s
        );
    // clang-format on
  };
};
int j = 0;
struct s2 {
  auto operator()() {
    auto f1 = [](auto e) {
      const auto [data] = e.Data();
      j = data;
    };
    auto f2 = [](auto e) {
      const auto [d1, d2] = e.Data();
      j = d1 + d2;
    };
    using namespace atv::sml;
    using namespace atv;
    // clang-format off
    return make_transition_table(
        *"s1"_s + event<e1> / f1 = "s2"_s, 
         "s2"_s + event<e6> / f2 = "s1"_s,
         "s1"_s + event<e6> / f2 = "s3"_s, 
         "s3"_s + event<e7> / f2 = "s1"_s);
    // clang-format on
  };
};

int k = 0;
struct s3 {
  auto operator()() {
    auto f1 = [](auto e) {
      const auto [data] = e.Data();
      k = data;
    };
    auto f2 = [](auto e) {
      const auto [d1, d2] = e.Data();
      k = d1 + d2;
    };
    using namespace atv::sml;
    using namespace atv;
    // clang-format off
    return make_transition_table(
        *"s1"_s + event<e1> / f1 = "s2"_s, 
         "s2"_s + event<e6> / f2 = "s1"_s,
         "s1"_s + event<e6> / f2 = "s3"_s, 
         "s3"_s + event<e7> / f2 = "s1"_s);
    // clang-format on
  };
};
namespace {
int n = 0;
}
struct s5 {
  auto operator()() {
    auto f1 = [](auto e) {
      const auto [data] = e.Data();
      n = data;
      k = n;
    };
    using namespace atv::sml;
    using namespace atv;
    // clang-format off
    return make_transition_table(
        *"s1"_s + event<e1> / f1 = "s2"_s 
        );
    // clang-format on
  }
};
struct active_msg_fixture {
  static auto Setup() -> void * {
    using namespace atv::sml;
    using namespace atv;
    active::Initialize(atv::Config<s2>(atv::logging),
                       atv::Config<s1>(threadsafe, atv::logging),
                       atv::Config<s5>(10_size, atv::logging));

    static active_msg_fixture f;
    return &f;
  };
  static auto Before(void *f) -> void {}
};

} // namespace
ZTEST(active_msg, test_handler) {
  using namespace active;
  static_assert(not std::is_same_v<e4::Service, e5::Service>);
  e1::Publish(666);
  zassert_true((i == j) and (i == 666), "i = %d, j = %d", i, j);
  zassert_true(k == 0);
  atv::ProcessQueuedEvents();
  zassert_equal(k, 666, "k=%d", k);
  e2::Publish(1, 1);
  zassert_equal(i, 2);
  zassert_false(j == 2);
  e2::Connect<e6>();
  e2::Publish(1, 1);
  zassert_equal(j, 2);
  e5::Connect<e1>();
  e5::Publish(5);
  zassert_true((i == j) and (i == 5));
  e6::Publish(1, 1);
  zassert_equal(j, 2);
  zassert_false(i == 2);
  zassert_equal(k, 666);
  atv::ProcessQueuedEvents();
}
namespace {
ACTIVE_EVENT_TYPE_DEF(e8);
ACTIVE_EVENT_TYPE_DEF(e9);

int action_calls = 0;
int action_no_calls = 0;
struct actions_guards {
  auto operator()() {
    using namespace atv::sml;
    return make_transition_table(*"idle"_s + event<e8> / [] {
      action_calls = 0;
      for (auto i = 0; i < 10; i++) {
        action_calls++;
        k_sleep(K_MSEC(200));
      }
      auto res = action_calls;
      call(res);
    } = "idle"_s);
  }
};
struct actions_no_guards {
  auto operator()() {
    using namespace atv::sml;
    return make_transition_table(*"idle"_s + event<e9> / [] {
      action_no_calls = 0;
      for (auto i = 0; i < 10; i++) {
        action_no_calls++;
        k_sleep(K_MSEC(200));
      }
      auto res = action_no_calls;
      no_guard_call(res);
    } = "idle"_s);
  }
};

K_THREAD_STACK_DEFINE(t0_stack, 512);
K_THREAD_STACK_DEFINE(t1_stack, 512);
K_THREAD_STACK_DEFINE(t2_stack, 512);
K_THREAD_STACK_DEFINE(t3_stack, 512);

constexpr auto num_t = 4;
k_thread t[num_t];
constexpr auto enum_event = 5;
} // namespace
ZTEST(active_msg, test_thread_safe) {
  active::Initialize(active::Config<actions_guards>(atv::threadsafe, atv::logging),
                     active::Config<actions_no_guards>(atv::logging));
  k_thread_create(
      &t[0], t0_stack, K_THREAD_STACK_SIZEOF(t0_stack),
      [](void *, void *, void *) {
        for (auto i = 0; i < enum_event; i++) {
          e8::Publish();
          k_sleep(K_MSEC(100));
        }
      },
      nullptr, nullptr, nullptr, 5, 0, K_FOREVER);
  k_thread_create(
      &t[1], t1_stack, K_THREAD_STACK_SIZEOF(t1_stack),
      [](void *, void *, void *) {
        for (auto i = 0; i < enum_event; i++) {
          e8::Publish();
          k_sleep(K_MSEC(100));
        }
      },
      nullptr, nullptr, nullptr, 5, 0, K_FOREVER);
  k_thread_create(
      &t[2], t2_stack, K_THREAD_STACK_SIZEOF(t2_stack),
      [](void *, void *, void *) {
        for (auto i = 0; i < enum_event; i++) {
          e9::Publish();
          k_sleep(K_MSEC(100));
        }
      },
      nullptr, nullptr, nullptr, 5, 0, K_FOREVER);
  k_thread_create(
      &t[3], t3_stack, K_THREAD_STACK_SIZEOF(t3_stack),
      [](void *, void *, void *) {
        for (auto i = 0; i < enum_event; i++) {
          e9::Publish();
          k_sleep(K_MSEC(100));
        }
      },
      nullptr, nullptr, nullptr, 5, 0, K_FOREVER);
  for (auto &d : t) {
    k_thread_start(&d);
  }
  for (auto &d : t) {
    k_thread_join(&d, K_FOREVER);
  }
  zassert_equal(call_fake.arg0_history[0], 10, "call_fake.arg0_history[0]=%d",
                call_fake.arg0_history[0]);
  zassert_equal(no_guard_call_fake.arg0_history[0], 19, "%d",
                no_guard_call_fake.arg0_history[0]);
}
ZTEST(active_msg, test_leak_memory) { e3::Publish(); }

ZTEST_SUITE(active_msg, nullptr, active_msg_fixture::Setup, nullptr, nullptr,
            nullptr);